{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": [
     "hide_input"
    ]
   },
   "outputs": [],
   "source": [
    "# Uncomment and run this cell if you're running in Google Colab.\n",
    "# !pip install ipywidgets\n",
    "# !pip install splink\n",
    "# !jupyter nbextension enable --py widgetsnbextension"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comparison values\n",
    "\n",
    "Run the following cell to get an interactive interface for trying out comparisons from the `comparison_library`.\n",
    "\n",
    "The interface uses the default arguments for each comparison, and can only be used for comparisons that refer to a single column\n",
    "\n",
    "In the following cells, you can get similar results for custom comparisons with an arbitrary number of input columns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f55c024a2d8c4888aad9ce906512a331",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Dropdown(description='Comparison:', options=('ExactMatch', 'LevenshteinAtThresholds', 'JaroAtTh…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import splink.comparison_library as cl\n",
    "from splink.internals.testing import comparison_vector_value, is_in_level\n",
    "from splink import DuckDBAPI\n",
    "import ipywidgets as widgets\n",
    "from IPython.display import Markdown\n",
    "\n",
    "\n",
    "def report_comparison_levels(comparison, values_dict, column_name):\n",
    "    db_api = DuckDBAPI()\n",
    "    levels = comparison.create_comparison_levels()\n",
    "    comparison_dict = comparison.get_comparison(\"duckdb\").as_dict()\n",
    "\n",
    "    table_rows = []\n",
    "    table_rows.append(\"| Match | Level | Description | SQL Condition |\")\n",
    "    table_rows.append(\"|-------|-------|-------------|---------------|\")\n",
    "\n",
    "    total_levels = len(levels)\n",
    "    matched_level = None\n",
    "    for i, level in enumerate(levels):\n",
    "        level_number = total_levels - i - 1\n",
    "        label = level.create_label_for_charts()\n",
    "        sql_condition = comparison_dict['comparison_levels'][i]['sql_condition']\n",
    "\n",
    "        is_match = is_in_level(level, values_dict, db_api)\n",
    "\n",
    "        if is_match and matched_level is None:\n",
    "            matched_level = level_number\n",
    "            match_indicator = \"✓\"\n",
    "            row = f\"| {match_indicator} | **{level_number}** | **{label}** | `{sql_condition}` |\"\n",
    "        else:\n",
    "            match_indicator = \"\"\n",
    "            row = f\"| {match_indicator} | {level_number} | {label} | `{sql_condition}` |\"\n",
    "\n",
    "        table_rows.append(row)\n",
    "\n",
    "    markdown_output = \"\\n\".join(table_rows)\n",
    "    return markdown_output\n",
    "\n",
    "def create_comparison_playground(column_name):\n",
    "\n",
    "    comparison_types = [\n",
    "        'ExactMatch', 'LevenshteinAtThresholds', 'JaroAtThresholds',\n",
    "        'JaroWinklerAtThresholds', 'DamerauLevenshteinAtThresholds',\n",
    "        'JaccardAtThresholds',\n",
    "        'AbsoluteDateDifferenceAtThresholds',\n",
    "        'ArrayIntersectAtSizes', 'DateOfBirthComparison',\n",
    "         'EmailComparison',\n",
    "        'NameComparison', 'PostcodeComparison'\n",
    "    ]\n",
    "\n",
    "    default_values = {\n",
    "        'ExactMatch': ('john', 'jon'),\n",
    "        'LevenshteinAtThresholds': ('smith', 'smyth'),\n",
    "        'JaroAtThresholds': ('martha', 'matha'),\n",
    "        'JaroWinklerAtThresholds': ('williams', 'willaims'),\n",
    "        'DamerauLevenshteinAtThresholds': ('receive', 'recieve'),\n",
    "        'CosineSimilarityAtThresholds': ('data science', 'science data'),\n",
    "        'JaccardAtThresholds': ('0123456789', '012345678'),\n",
    "        'AbsoluteDateDifferenceAtThresholds': ('2023-01-01', '2023-01-15'),\n",
    "        'ArrayIntersectAtSizes': ('apple,banana,cherry', 'banana,cherry,date'),\n",
    "        'DateOfBirthComparison': ('1990-05-15', '1990-05-16'),\n",
    "        'EmailComparison': ('john.doe@example.com', 'john.doe@other.com'),\n",
    "        'NameComparison': ('Elizabeth', 'Elisabeth'),\n",
    "        'PostcodeComparison': ('SW1A 1AA', 'SW1A 1AB')\n",
    "    }\n",
    "\n",
    "    db_api = DuckDBAPI()\n",
    "\n",
    "    docstrings = {}\n",
    "    for comp_type in comparison_types:\n",
    "        class_obj = getattr(cl, comp_type)\n",
    "        init_doc = getattr(class_obj.__init__, '__doc__', None)\n",
    "        docstrings[comp_type] = init_doc if init_doc else class_obj.__doc__\n",
    "\n",
    "\n",
    "    def get_comparison(comp_type):\n",
    "        if comp_type in ['DateOfBirthComparison']:\n",
    "\n",
    "            return getattr(cl, comp_type)(column_name, input_is_string=True)\n",
    "        if comp_type == 'AbsoluteDateDifferenceAtThresholds':\n",
    "            return getattr(cl, comp_type)(column_name, input_is_string=True, metrics=[\"day\", \"month\"], thresholds=[1, 1])\n",
    "        elif comp_type in ['EmailComparison', 'ForenameSurnameComparison', 'NameComparison', 'PostcodeComparison', 'ArrayIntersectAtSizes']:\n",
    "            return getattr(cl, comp_type)(column_name)\n",
    "        else:\n",
    "            return getattr(cl, comp_type)(column_name)\n",
    "\n",
    "    def run_comparison(change):\n",
    "        left_value = left_input.value if left_input.value != \"\" else None\n",
    "        right_value = right_input.value if right_input.value != \"\" else None\n",
    "        comparison = get_comparison(comparison_select.value)\n",
    "\n",
    "        if comparison_select.value == 'ArrayIntersectAtSizes':\n",
    "            left_value = left_value.split(',') if left_value else None\n",
    "            right_value = right_value.split(',') if right_value else None\n",
    "\n",
    "        values_dict = {f\"{column_name}_l\": left_value, f\"{column_name}_r\": right_value}\n",
    "\n",
    "        output.clear_output()\n",
    "        markdown_output = report_comparison_levels(comparison, values_dict, column_name)\n",
    "        with output:\n",
    "            display(Markdown(\"### Comparison levels:\"))\n",
    "            display(Markdown(markdown_output))\n",
    "\n",
    "            docstring = docstrings.get(comparison_select.value, \"No docstring available\")\n",
    "            processed_docstring = \"\\n\".join(line.strip() for line in docstring.split(\"\\n\"))\n",
    "            display(Markdown(\"### Comparison Function Docstring:\"))\n",
    "            display(Markdown(processed_docstring))\n",
    "\n",
    "\n",
    "        # Store the markdown output for later use\n",
    "        playground.markdown_output = markdown_output\n",
    "\n",
    "\n",
    "\n",
    "    def on_comparison_change(change):\n",
    "        new_value = change['new']\n",
    "        left_value, right_value = default_values.get(new_value, ('', ''))\n",
    "\n",
    "        # Temporarily unobserve the input widgets\n",
    "        left_input.unobserve(run_comparison, names='value')\n",
    "        right_input.unobserve(run_comparison, names='value')\n",
    "\n",
    "        # Update the values\n",
    "        left_input.value = left_value\n",
    "        right_input.value = right_value\n",
    "\n",
    "        # Re-observe the input widgets\n",
    "        left_input.observe(run_comparison, names='value')\n",
    "        right_input.observe(run_comparison, names='value')\n",
    "\n",
    "        # Run the comparison once after updating both inputs\n",
    "        run_comparison(None)\n",
    "\n",
    "\n",
    "    comparison_select = widgets.Dropdown(\n",
    "        options=comparison_types,\n",
    "        value='ExactMatch',\n",
    "        description='Comparison:',\n",
    "    )\n",
    "    left_input = widgets.Text(description=f\"{column_name} Left:\", value=default_values['ExactMatch'][0])\n",
    "    right_input = widgets.Text(description=f\"{column_name} Right:\", value=default_values['ExactMatch'][1])\n",
    "    output = widgets.Output()\n",
    "\n",
    "    comparison_select.observe(on_comparison_change, names='value')\n",
    "    for widget in (comparison_select, left_input, right_input):\n",
    "        widget.observe(run_comparison, names='value')\n",
    "\n",
    "    # Call run_comparison immediately to compute initial output\n",
    "    playground = widgets.VBox([comparison_select, left_input, right_input, output])\n",
    "\n",
    "    run_comparison(None)\n",
    "\n",
    "    return playground\n",
    "\n",
    "playground = create_comparison_playground(\"column\")\n",
    "display(playground)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "| Match | Level | Description | SQL Condition |\n",
       "|-------|-------|-------------|---------------|\n",
       "|  | 4 | first_name is NULL | `\"first_name_l\" IS NULL OR \"first_name_r\" IS NULL` |\n",
       "|  | 3 | Exact match on first_name | `\"first_name_l\" = \"first_name_r\"` |\n",
       "| ✓ | **2** | **Match on reversed cols: first_name and surname** | `first_name_l = surname_r` |\n",
       "|  | 1 | Jaro-Winkler distance of first_name >= 0.8 | `jaro_winkler_similarity(\"first_name_l\", \"first_name_r\") >= 0.8` |\n",
       "|  | 0 | All other comparisons | `ELSE` |"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import splink.comparison_level_library as cll\n",
    "\n",
    "first_name_comparison = cl.CustomComparison(\n",
    "    comparison_levels=[\n",
    "        cll.NullLevel(\"first_name\"),\n",
    "        cll.ExactMatchLevel(\"first_name\"),\n",
    "        {\n",
    "            \"sql_condition\": \"first_name_l = surname_r\",\n",
    "            \"label_for_charts\": \"Match on reversed cols: first_name and surname\",\n",
    "        },\n",
    "        cll.JaroWinklerLevel(\"first_name\", 0.8),\n",
    "        cll.ElseLevel(),\n",
    "    ]\n",
    ")\n",
    "# Need to be able to pass values in as a dict {\"first_name_l\": \"Robin\", \"first_name_r\": \"Robyn\", \"surname_l\": \"Linacre\", \"surname_r\": \"Linacre\"}\n",
    "values = {\n",
    "    \"first_name_l\": \"Robin\",\n",
    "    \"first_name_r\": \"Linacre\",\n",
    "    \"surname_l\": \"Linacre\",\n",
    "    \"surname_r\": \"Robin\",\n",
    "}\n",
    "display(Markdown(report_comparison_levels(first_name_comparison, values, \"first_name\")))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
